package cc.shacocloud.mirage.restful;

import cc.shacocloud.mirage.restful.bind.annotation.ExceptionHandler;
import cc.shacocloud.mirage.utils.ClassUtil;
import cc.shacocloud.mirage.utils.annotation.AnnotatedElementUtils;
import cc.shacocloud.mirage.utils.comparator.AnnotationOrderComparator;
import cc.shacocloud.mirage.utils.comparator.Ordered;
import cc.shacocloud.mirage.utils.map.ConcurrentReferenceHashMap;
import cc.shacocloud.mirage.utils.reflection.ReflectUtil;
import io.vertx.core.Future;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Predicate;

/**
 * 异常处理方法解析。
 * 在给定的类中发现表示注解{@linkplain ExceptionHandler}的方法，
 * 包含它的所有超类，并帮助将给定的{@link Exception}解析为给定的{@link Method}支持的异常类型。
 */
public class ExceptionHandlerMethodResolver implements Ordered {
    
    /**
     * 用于选择{@code @ExceptionHandler}方法的过滤器。
     */
    public static final Predicate<Method> METHOD_PREDICATE = method -> AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
    
    private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(32);
    
    private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);
    
    @Getter
    private final Object exceptionHandler;
    
    /**
     * 查找给定类型中的{@link ExceptionHandler}方法的构造函数。
     */
    public ExceptionHandlerMethodResolver(@NotNull Object exceptionHandler) {
        Class<?> handlerType = exceptionHandler.getClass();
        
        for (Method method : ReflectUtil.getMethods(handlerType, METHOD_PREDICATE)) {
            
            for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
                addExceptionMapping(exceptionType, method);
            }
        }
        this.exceptionHandler = exceptionHandler;
    }
    
    /**
     * 从{@code @ExceptionHandler}注解中提取异常映射
     */
    private @NotNull List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
        List<Class<? extends Throwable>> result = new ArrayList<>();
        detectAnnotationExceptionMappings(method, result);
        
        if (result.isEmpty()) {
            throw new IllegalStateException("没有映射到的异常类型： " + method);
        }
        
        return result;
    }
    
    private void detectAnnotationExceptionMappings(Method method, @NotNull List<Class<? extends Throwable>> result) {
        ExceptionHandler ann = AnnotatedElementUtils.getAnnotation(method, ExceptionHandler.class);
        assert ann != null;
        
        if (!ClassUtil.isAssignable(Future.class, method.getReturnType())) {
            throw new IllegalArgumentException("异常处理器[" + method.getDeclaringClass() + "]声明的方法[" + method + "]不合法，" +
                    "@ExceptionHandler 注解标识的方法返回值必须是 io.vertx.core.Future或其子类! 实际类型");
        }
        
        result.addAll(Arrays.asList(ann.value()));
    }
    
    private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
        Method oldMethod = this.mappedMethods.put(exceptionType, method);
        if (oldMethod != null && !oldMethod.equals(method)) {
            throw new IllegalStateException("不明确的 @ExceptionHandler 注解方法映射:  [" + exceptionType + "]: {" + oldMethod + ", " + method + "}");
        }
    }
    
    /**
     * 所包含的类型是否有异常映射。
     */
    public boolean hasExceptionMappings() {
        return !this.mappedMethods.isEmpty();
    }
    
    /**
     * 找到一个{@link Method}来处理给定的异常。
     * 如果找到多个匹配项，使用{@link ExceptionDepthComparator}
     *
     * @param exception 异常
     * @return 处理异常的方法;如果没有找到异常，则使用{@code null}
     */
    @Nullable
    public Method resolveMethod(Exception exception) {
        return resolveMethodByThrowable(exception);
    }
    
    /**
     * 找到一个{@link Method}来处理给定的 {@link Throwable}
     * 如果找到多个匹配项，使用{@link ExceptionDepthComparator}
     *
     * @param exception 异常
     * @return 处理异常的方法;如果没有找到异常，则使用{@code null}
     */
    @Nullable
    public Method resolveMethodByThrowable(Throwable exception) {
        Method method = resolveMethodByExceptionType(exception.getClass());
        if (method == null) {
            Throwable cause = exception.getCause();
            if (cause != null) {
                method = resolveMethodByExceptionType(cause.getClass());
            }
        }
        return method;
    }
    
    /**
     * 找到一个{@link Method}来处理给定的异常类型。
     *
     * @param exceptionType 异常类型
     * @return 处理异常的方法;如果没有找到异常，则使用{@code null}
     */
    @Nullable
    public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
        Method method = this.exceptionLookupCache.get(exceptionType);
        if (method == null) {
            method = getMappedMethod(exceptionType);
            if (method != null) {
                this.exceptionLookupCache.put(exceptionType, method);
            }
        }
        return method;
    }
    
    /**
     * 返回映射到给定异常类型的{@link Method}，如果没有，则返回{@code null}。
     */
    @Nullable
    private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
        List<Class<? extends Throwable>> matches = new ArrayList<>();
        for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
            if (mappedException.isAssignableFrom(exceptionType)) {
                matches.add(mappedException);
            }
        }
        if (!matches.isEmpty()) {
            matches.sort(new ExceptionDepthComparator(exceptionType));
            return this.mappedMethods.get(matches.get(0));
        } else {
            return null;
        }
    }
    
    @Override
    public int getOrder() {
        return AnnotationOrderComparator.INSTANCE.getOrder(exceptionHandler.getClass(), 0);
    }
}
